using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Content;
using TetraEngine;
using System.Text;
using System.Diagnostics;

namespace Tetrazune
{
    public enum TetraGameInput
    {
        MoveLeft = 0,
        MoveRight = 1,
        Drop = 2,
        RotateCW = 3,
        RotateCCW = 4,
        Pause = 5,
        Nothing = 6
    }

    /// <summary>
    /// This wraps a TetraGame and gives the user interface for it.
    /// </summary>
    public class TetraGameManager : DrawableGameComponent
    {
        public event EventHandler GameComplete;

        private readonly Vector2 BoardLocation = new Vector2(24, 8);
        private readonly int BlockSize = 12;
        
        private readonly int DigitWidth = 12;
        
        private readonly Vector2 ScoreLocation = new Vector2(222, 44);
        private readonly int MaxScore = 99999999;
        private readonly int MaxScoreDigits = 8;

        private readonly Vector2[] ZuneLocation = new Vector2[] { 
            new Vector2(196, 118), new Vector2(274, 118), new Vector2(196, 155), 
            new Vector2(274, 155), new Vector2(196, 192) };
        private readonly int MaxZunes = 999;
        private readonly int MaxZuneDigits = 3;
        
        private readonly Vector2 LevelLocation = new Vector2(250, 70);
        private readonly Color LevelColor = new Color(173, 173, 173);

        private readonly Vector2 GameOverLocation = new Vector2(39, 108);

        private readonly TimeSpan clearInterval = TimeSpan.FromMilliseconds(800);
        private readonly TimeSpan clearAnimInterval = TimeSpan.FromMilliseconds(1000);

        private readonly TimeSpan InitialDelay = TimeSpan.FromMilliseconds(600);
        private readonly TimeSpan HoldDelay = TimeSpan.FromMilliseconds(100);

        private Zune.Zune zune;
        private TetraGame tetra;
        private TimeSpan stepInterval;
        private TimeSpan remainder = TimeSpan.Zero;
        private bool paused = false;

        private SpriteBatch spb;
        private Texture2D background;
        private Texture2D[] numbers; //array of all 0-9 LED-Style digits.
        private Texture2D texGameOver;

        private SpriteFont levelFont;

        private readonly int BlockCount = 8;
        private Texture2D[] blockTex; //array of all block_X.png images

        private ControlMapping controls;

        private TimeDelay[] delays = new TimeDelay[6];
        private TimeSpan pressTime;

        private Sparkler sparkler;
        private Texture2D[] zunes = new Texture2D[5]; //array of zune icons

        private GameOver gameover;

        public TetraGameManager(Game game, Zune.Zune zune, ControlMapping controls)
            : base(game)
        {
            tetra = new TetraGame();
            UpdateSpeed();
            this.controls = controls;
            this.zune = zune;
            sparkler = new Sparkler(Game);
            Game.Components.Add(sparkler);
            sparkler.DrawOrder = this.DrawOrder + 1;
            zune.ButtonDown += new Zune.ZuneButtonEventHandler(zune_ButtonDown);
            zune.ButtonPressed += new Zune.ZuneButtonEventHandler(zune_ButtonPressed);
            zune.ButtonUp += new Zune.ZuneButtonEventHandler(zune_ButtonUp);
            this.DrawOrderChanged += new EventHandler(TetraGameManager_DrawOrderChanged);
            this.EnabledChanged += new EventHandler(TetraGameManager_EnabledChanged);
            gameover = new GameOver(tetra);
        }

        void TetraGameManager_EnabledChanged(object sender, EventArgs e)
        {
            this.remainder = TimeSpan.Zero;
        }


        public void Reset()
        {
            tetra.Reset();
            UpdateSpeed();
            remainder = TimeSpan.Zero;
        }


        public override void Initialize()
        {
            base.Initialize();
        }

        protected override void LoadContent()
        {
            base.LoadContent();
            spb = new SpriteBatch(this.GraphicsDevice);
            blockTex = new Texture2D[BlockCount];
            numbers = new Texture2D[10];
            for (int i = 0; i < BlockCount; i++)
                blockTex[i] = Game.Content.Load<Texture2D>("blocks/block_" + i);
            for (int i = 0; i < 10; i++)
                numbers[i] = Game.Content.Load<Texture2D>("leddigits/d" + i);
            background = Game.Content.Load<Texture2D>("bg");
            levelFont = Game.Content.Load<SpriteFont>("Pesca");
            for (int i = 0; i < 5; i++)
                zunes[i] = Game.Content.Load<Texture2D>("zunes/zune" + (i + 1).ToString());
            texGameOver = Game.Content.Load<Texture2D>("gameover");
        }

        public override void Draw(GameTime gameTime)
        {
            spb.Begin(SpriteBlendMode.AlphaBlend);
            spb.Draw(background, Vector2.Zero, Color.White);

            if (!paused)
            {
                if (gameover.ScreenWasFilled)
                    spb.Draw(texGameOver, GameOverLocation, Color.White);
                if (tetra.Piece != null)
                {
                    BlockGrid pieceGrid = tetra.Piece.GetBlockGrid();
                    for (int i = 0; i < pieceGrid.Size; i++)
                        for (int j = 0; j < pieceGrid.Size; j++)
                            DrawBlock(spb, pieceGrid[i, j],
                                tetra.PieceLocation.X + j, tetra.PieceLocation.Y + i);
                }
                for (int i = 0; i < GameBoard.GameBoardRows; i++)
                    for (int j = 0; j < GameBoard.GameBoardColumns; j++)
                        DrawBlock(spb, tetra.GameBoard[i, j], j, i);
            }


            DrawInfo(spb);

            if (paused)
                throw new NotImplementedException();//render pause menu!

            spb.End();
            base.Draw(gameTime);
        }

        public override void Update(GameTime gameTime)
        {
            if (tetra.GameOver)
            {
                gameover.Update(gameTime);
                if (gameover.Finished)
                {
                    this.Enabled = false;
                    this.Visible = false;
                    if (GameComplete != null)
                        GameComplete(this, new EventArgs());
                }
                return;
            }

            remainder += gameTime.ElapsedGameTime;
            if (tetra.ClearRows.Length > 0)
            {
                RowClearAnim(tetra.ClearRows);
                if (remainder < clearInterval) return;
                remainder -= clearInterval;
                tetra.Step();
                UpdateSpeed();
                return;
            }

            if (remainder < stepInterval) return;
            tetra.Step();
            remainder -= stepInterval;


            base.Update(gameTime);
        }


        private void UpdateSpeed()
        {
            if (tetra.Level <= 15)
                stepInterval = TimeSpan.FromMilliseconds(915 - (60 * tetra.Level));
            else
                stepInterval = TimeSpan.FromMilliseconds(10);
        }

        private void DrawBlock(SpriteBatch spriteBatch, int blockVal, int gridX, int gridY)
        {
            if (blockVal < 1) return;
            Vector2 loc = new Vector2(
                BoardLocation.X + (gridX * BlockSize),
                BoardLocation.Y + (gridY * BlockSize));
            spriteBatch.Draw(blockTex[blockVal - 1], loc, Color.White);
        }

        private void DrawInfo(SpriteBatch spriteBatch)
        {
            spriteBatch.DrawString(levelFont, tetra.Level.ToString(), 
                LevelLocation + (new Vector2(-2, 2)), Color.Black);
            spriteBatch.DrawString(levelFont, tetra.Level.ToString(),
                LevelLocation, LevelColor);

            int value = Math.Min(tetra.Score, MaxScore);
            DrawDigits(value, MaxScoreDigits, spriteBatch, ScoreLocation);

            value = Math.Min(tetra.Singles, MaxZunes);
            DrawDigits(value, MaxZuneDigits, spriteBatch, ZuneLocation[0]);
            value = Math.Min(tetra.Doubles, MaxZunes);
            DrawDigits(value, MaxZuneDigits, spriteBatch, ZuneLocation[1]);
            value = Math.Min(tetra.Triples, MaxZunes);
            DrawDigits(value, MaxZuneDigits, spriteBatch, ZuneLocation[2]);
            value = Math.Min(tetra.Tetras, MaxZunes);
            DrawDigits(value, MaxZuneDigits, spriteBatch, ZuneLocation[3]);
            value = Math.Min(tetra.Specials, MaxZunes);
            DrawDigits(value, MaxZuneDigits, spriteBatch, ZuneLocation[4]);
        }

        private void DrawDigits(int value, int digits, SpriteBatch spriteBatch, Vector2 location)
        {
            for (int i = 0; i < digits; i++, location.X += DigitWidth)
                spriteBatch.Draw(
                    numbers[(value / (int)Math.Pow(10, (digits - 1) - i)) % 10],
                    location,
                    Color.White);
        }

        private void RowClearAnim(int[] clearRows)
        {
            if (clearRows.Length < 1 || sparkler.Sparkles.Count > 0) return;
            Vector2 p = new Vector2();
            int blockHalf = BlockSize / 2;
            for (int i = 0; i < clearRows.Length; i++)
            {
                int row = clearRows[i];
                for (int j = 0; j < GameBoard.GameBoardColumns; j++)
                {
                    p.X = BoardLocation.X + j * BlockSize - blockHalf;
                    p.Y = BoardLocation.Y + row * BlockSize - blockHalf;
                    sparkler.Sparkles.Add(
                        new Sparkplosion(p, 4, blockHalf, (BlockSize * 2), clearAnimInterval)
                        );
                }
            }
            CreateLineClearSprite(clearRows);
            if (tetra.TetraSpecial) CreateSpecialClearSprite();
        }

        private void CreateSpecialClearSprite()
        {
            Vector2 boardCenter = new Vector2(
                BoardLocation.X + ((BlockSize * GameBoard.GameBoardColumns) / 2),
                BoardLocation.Y + ((BlockSize * GameBoard.GameBoardRows) / 2));
            Vector2 spriteHalf = new Vector2(zunes[4].Width / 2, zunes[4].Height / 2);
            SlideSprite specIcon = new SlideSprite(
                zunes[4], this.Game,
                boardCenter - spriteHalf, ZuneLocation[4] - spriteHalf);
            Game.Components.Add(specIcon);
            sparkler.Sparkles.Add(new Sparkplosion(
                boardCenter, 30, 20, 80, TimeSpan.FromMilliseconds(650)));
        }

        private void CreateLineClearSprite(int[] cleared)
        {
            int index = cleared.Length - 1;
            Vector2 spriteHalf = new Vector2(zunes[index].Width / 2, zunes[index].Height / 2);
            Vector2 iconLoc = BoardLocation;
            iconLoc.X += BlockSize * GameBoard.GameBoardColumns;
            iconLoc.Y += BlockSize * (cleared[0] + cleared.Length / 2);
            iconLoc.X -= 3;
            iconLoc.Y -= zunes[index].Height / 2;
            SlideSprite zuneIcon = new SlideSprite(
                zunes[index], this.Game,
                iconLoc, ZuneLocation[index] - spriteHalf);
            Game.Components.Add(zuneIcon);
        }


        private void zune_ButtonUp(object sender, Zune.ZuneButtonEventArgs e)
        {
            TetraGameInput input = controls[e.Button];
            if (input == TetraGameInput.Pause || input == TetraGameInput.Nothing)
            {
                ProcessInput(input);
                return;
            }
            delays[(int)input].Reset();
        }

        private void zune_ButtonPressed(object sender, Zune.ZuneButtonEventArgs e)
        {
            TetraGameInput input = controls[e.Button];
            if (input == TetraGameInput.Pause || input == TetraGameInput.Nothing)
                return;
            if (e.GameTime.TotalGameTime == pressTime) 
                return; //prevents update when button is first pressed.

            if (delays[(int)input].DelayPassed(e.GameTime.ElapsedGameTime))
            {
                delays[(int)input].DelayTime = HoldDelay;
                ProcessInput(input);
            }
        }

        private void zune_ButtonDown(object sender, Zune.ZuneButtonEventArgs e)
        {
            TetraGameInput input = controls[e.Button];
            if (input == TetraGameInput.Pause || input == TetraGameInput.Nothing)
                return;
            pressTime = e.GameTime.TotalGameTime;
            delays[(int)input].DelayTime = InitialDelay;
            delays[(int)input].Reset();
            ProcessInput(input);
        }

        private void ProcessInput(TetraGameInput input)
        {
            switch (input)
            {
                case TetraGameInput.MoveLeft:
                    tetra.MoveLeft();
                    break;
                case TetraGameInput.MoveRight:
                    tetra.MoveRight();
                    break;
                case TetraGameInput.Drop:
                    tetra.DropPiece();
                    break;
                case TetraGameInput.RotateCW:
                    tetra.RotatePieceCW();
                    break;
                case TetraGameInput.RotateCCW:
                    tetra.RotatePieceCCW();
                    break;
                case TetraGameInput.Pause:
                    paused = !paused;
                    break;
                case TetraGameInput.Nothing:
                    break;
            }
        }

        private void TetraGameManager_DrawOrderChanged(object sender, EventArgs e)
        {
            sparkler.DrawOrder = this.DrawOrder + 1;
        }
    }
    
    public class ControlMapping
    {
        private Dictionary<Zune.ZuneButtons, TetraGameInput> map =
            new Dictionary<Zune.ZuneButtons, TetraGameInput>();

        public ControlMapping()
        {
            map.Add(Zune.ZuneButtons.Left, TetraGameInput.MoveLeft);
            map.Add(Zune.ZuneButtons.Right, TetraGameInput.MoveRight);
            map.Add(Zune.ZuneButtons.PlayPause, TetraGameInput.Pause);
            map.Add(Zune.ZuneButtons.Down, TetraGameInput.Drop);
            map.Add(Zune.ZuneButtons.Up, TetraGameInput.RotateCW);
            map.Add(Zune.ZuneButtons.Select, TetraGameInput.RotateCCW);
            map.Add(Zune.ZuneButtons.Back, TetraGameInput.Nothing);
        }

        public TetraGameInput this[Zune.ZuneButtons button]
        {
            get { return map[button]; }
            set { map[button] = value; }
        }

        public String Serialize()
        {
            String str = "";
            foreach (Zune.ZuneButtons btn in map.Keys )
            {
                str += btn + "=" + map[btn] + " ";
            }
            return str;
        }
        public static ControlMapping Deserialize(String data)
        {
            ControlMapping map = new ControlMapping();
            try
            {
                String[] items = data.Split(' ');
                map.map.Clear();
                foreach (String s in items)
                {
                    int key = int.Parse(s.Split('=')[0]);
                    int val = int.Parse(s.Split('=')[1]);
                    map.map.Add((Zune.ZuneButtons)key, (TetraGameInput)val);
                }
            }
            catch
            {
                return new ControlMapping();
            }
            return map;
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            foreach (Zune.ZuneButtons btn in map.Keys)
            {
                sb.Append("<");
                sb.Append(btn.ToString());
                sb.Append(", ");
                sb.Append(map[btn].ToString());
                sb.Append("> ");
            }
            return sb.ToString();
        }
    }

    /// <summary>
    /// Keeps track of an amount of time to wait, and notifies when that amount of time has passed.
    /// </summary>
    public struct TimeDelay
    {
        private TimeSpan remainder;

        /// <summary>
        /// Initializes a new TimeDelay
        /// </summary>
        /// <param name="delayTime">Initial value of DelayTime</param>
        public TimeDelay(TimeSpan delayTime)
            : this()
        {
            DelayTime = delayTime;
            Reset();
        }

        /// <summary>
        /// The amount of time to wait before Update returns true
        /// </summary>
        public TimeSpan DelayTime;

        /// <summary>
        /// Checks how much time has passed, and returns true if it is more than DelayTime
        /// </summary>
        /// <param name="elapsed">The amount of time that has elapsed.</param>
        /// <returns>True if DelayTime or greater time has passed, false otherwise</returns>
        public bool DelayPassed(TimeSpan elapsed)
        {
            remainder += elapsed;
            if (remainder < DelayTime) return false;
            remainder -= DelayTime;
            return true;
        }

        /// <summary>
        /// Resets the saved time passed to zero.
        /// </summary>
        public void Reset()
        {
            remainder = TimeSpan.Zero;
        }
    }

}